﻿///////////////////////////////////////////////////////////////////////////////
// File:        SelectReactor.cc
// Author:      671643387@qq.com
// Description:
///////////////////////////////////////////////////////////////////////////////

#include <cassert>
#include <memory>
#include <nio/base/TimeUtil.hpp>

#include "nio/net/detail/SelectReactor.hpp"
using namespace nio::net;
using namespace nio::net::detail;

SelectReactor::SelectReactor()
    : Reactor()
    , lastHeartbeatTime_(0)
    , sources_(512)
{
    memset(&readFds_, 0, sizeof(readFds_));
    memset(&writeFds_, 0, sizeof(writeFds_));
    memset(&errorFds_, 0, sizeof(errorFds_));
}

SelectReactor::~SelectReactor()
{
    assert(sources_.empty());
}

bool SelectReactor::addSource(sock_t sock, Event events, EventHandler* handler)
{
    assert(sock != INVALID_SOCKET && handler != NULL);
    if (!(events & RE_ALL))
        return false;

    if (sources_.find(sock) != sources_.end())
        return false;

    if (events & RE_READ)
        FD_SET(sock, &readFds_[BAK]);
    if (events & RE_WRITE)
        FD_SET(sock, &writeFds_[BAK]);
    if (events & RE_ERROR)
        FD_SET(sock, &errorFds_[BAK]);

    sources_[sock] = handler;
    return true;
}

void SelectReactor::removeSource(sock_t sock)
{
    assert(sock != INVALID_SOCKET);
    FD_CLR(sock, &readFds_[BAK]);
    FD_CLR(sock, &writeFds_[BAK]);
    FD_CLR(sock, &errorFds_[BAK]);
    SourceMap::iterator it = sources_.find(sock);
    if (it != sources_.end())
        sources_.erase(it);
}

void SelectReactor::doTick(nio::base::time_t timeout)
{
    bool doHeartbeat = false;
    nio::base::time_t now = nio::base::TimeUtil::fromStartup();
    if ((now - lastHeartbeatTime_) > cnfHeartbeatTime_)
    {
        lastHeartbeatTime_ = now;
        doHeartbeat = true;
    }

    struct timeval tm;
    tm.tv_sec = timeout / 1000;
    tm.tv_usec = (timeout % 1000) * 1000;
    readFds_[USE] = readFds_[BAK];
    writeFds_[USE] = writeFds_[BAK];
    errorFds_[USE] = errorFds_[BAK];

    if (::select(0, &readFds_[USE], &writeFds_[USE], &errorFds_[USE], &tm) > 0)
    {
        SourceMap::iterator it = sources_.begin();
        SourceMap::iterator end = sources_.end();
        for (; it != end; ++it)
        {
            sock_t sock = it->first;

            if (FD_ISSET(sock, &errorFds_[USE]))
                it->second->handleError();
            if (FD_ISSET(sock, &readFds_[USE]))
                it->second->handleInput();
            if (FD_ISSET(sock, &writeFds_[USE]))
                it->second->handleOutput();
            if (doHeartbeat)
                it->second->handleHeartbeat();
        }
    }
}


